/*
 * Copyright 2009-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License i distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.jade.statement.interpreter;

import net.paoding.rose.jade.annotation.condition.SQLCondition;
import net.paoding.rose.jade.cache.JadeCache;
import net.paoding.rose.jade.statement.StatementRuntime;
import net.paoding.rose.jade.statement.jexl.Jexl3Analysis;
import net.paoding.rose.jade.statement.jexl.Jexl3Execute;
import net.paoding.rose.jade.statement.jexl.analysis.Analysis;
import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.BadSqlGrammarException;

import java.sql.SQLSyntaxErrorException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * commons-jexl3 解析器
 * @author fusheng.zhang
 */
public class Jexl3Interpreter implements Interpreter {

    private final JadeCache<String, List<Analysis>> jadeCache;

    public Jexl3Interpreter(JadeCache<String, List<Analysis>> jadeCache) {
        this.jadeCache = jadeCache;
    }

    @Override
    public void interpret(StatementRuntime runtime) {
        String sqlExpression = runtime.getSQL();
        try {
            Map<String, Object> parameters = runtime.getParameters();
            Map<String, ?> constants = runtime.getMetaData().getDAOMetaData().getConstants();
            //
            List<Analysis> analyses = jadeCache.get(sqlExpression)
                    .orElseGet(() -> new Jexl3Analysis(sqlExpression).compile());
            Jexl3Execute jexl3Execute = new Jexl3Execute(parameters, constants);
            analyses.forEach(analysis -> analysis.execute(jexl3Execute));
            StringBuilder sql = new StringBuilder(jexl3Execute.getResult());
            //
            runtime.setArgs(jexl3Execute.getArgs().toArray());
            runtime.setSQL(sql.toString());
            // 转换 and or like 等 表达式
            SQLCondition[] sqlConditions = runtime.getSQLCondition();
            if (Objects.isNull(sqlConditions) || sqlConditions.length <= 0) return;
            AtomicBoolean appendWhere = new AtomicBoolean(true);

            Arrays.stream(sqlConditions).forEach(sqlCondition -> {
                String condition = sqlCondition.condition();
                boolean conditionNotBlank = StringUtils.isNotBlank(condition);
                ArrayList<String> valueList = new ArrayList<>(Arrays.asList(sqlCondition.values()));
                valueList.add(sqlCondition.value());
                valueList.stream().filter(StringUtils::isNotBlank).forEach(expression -> {
                    //
                    List<Analysis> sqlAndAnalysis = jadeCache.get(expression)
                            .orElseGet(() -> new Jexl3Analysis(expression).compile());
                    Jexl3Execute sqlAndExecute = new Jexl3Execute(parameters, constants);
                    sqlAndAnalysis.forEach(analysis -> analysis.execute(sqlAndExecute));
                    String sqlConditionStr = sqlAndExecute.getResult();
                    if (StringUtils.isNotBlank(sqlConditionStr)) {
                        if (appendWhere.get() && conditionNotBlank && sqlCondition.appendWhere()) {
                            sql.append(" where ").append(sqlConditionStr);
                            appendWhere.set(false);
                        } else {
                            sql.append(" ").append(condition).append(" ").append(sqlConditionStr);
                        }
                        runtime.setArgs(sqlAndExecute.getArgs().toArray());
                    }
                });
            });
            runtime.setSQL(sql.toString());
        } catch (Exception e) {
            String daoInfo = runtime.getMetaData().toString();
            throw new BadSqlGrammarException(daoInfo, sqlExpression,
                    new SQLSyntaxErrorException(daoInfo + " @SQL('" + sqlExpression + "')", e));
        }

    }

}
